#ifndef _Ape__IntBase_h
#define _Ape__IntBase_h

#include <Ape/Object.h>
#include <Ape/String.h>

#include <Ape/Collections/Stack.h>
#include <Ape/EInvalidFormat.h>
#include <Ape/EInvalidArgument.h>

namespace Ape {
	static String Int_Digits (L"0123456789ABCDEF");
	
	enum Radix {
		radix2 = 2,
		radix3 = 3,
		radix4 = 4,
		radix5 = 5,
		radix6 = 6,
		radix7 = 7,
		radix8 = 8,
		radix9 = 9,
		radix10 = 10,
		radix11 = 11,
		radix12 = 12,
		radix13 = 13,
		radix14 = 14,
		radix15 = 15,
		radix16 = 16,
		
		radixBin = 2,
		radixOct = 8,
		radixDec = 10,
		radixHex = 16
	};
	
	template <typename int_t>
	class IntBase : public Object {
		TYPE_NAME ("Ape::IntBase<int_t>")
	private:
		int_t m_Value;
		int_t m_Zero;
	public:
	
		IntBase();
		IntBase (int_t value);
		IntBase (const IntBase<int_t>& other);
		virtual ~IntBase();
		
		int_t Value() const;
		operator int_t() const;
		//operator unsigned long() const;
		
		virtual const String ToString() const;
		const String ToString (Radix radix, size_t fixedWidth = 0) const;
		
		IntBase<int_t> power (int iPower) const;
		
		static IntBase<int_t> Parse (const String& s, Radix radix);
	};
	
	template <typename int_t>
	IntBase<int_t>::IntBase() : m_Value (0), m_Zero (0) {}
	template <typename int_t>
	IntBase<int_t>::~IntBase() {}
	
	template <typename int_t>
	IntBase<int_t>::IntBase (const IntBase<int_t>& other)
			: Object (other), m_Value (other.m_Value), m_Zero (other.m_Zero)
	{}
	
	template <typename int_t>
	IntBase<int_t>::IntBase (int_t value) : m_Value (value), m_Zero (0) {}
	
	template <typename int_t>
	IntBase<int_t>::operator int_t() const {
		return m_Value;
	}
	
	template <typename int_t>
	int_t IntBase<int_t>::Value() const {
		return m_Value;
	}
	
	template <typename int_t>
	const String IntBase<int_t>::ToString() const {
		return ToString (radixDec);
	}
	
	template <typename int_t>
	const String IntBase<int_t>::ToString (Radix radix, size_t fixedWidth) const {
		if (Value() == m_Zero) {
			if (!fixedWidth)
				return L"0";
				
			// fixed width
			wchar_t* buff = new wchar_t[fixedWidth + 1];
			buff[fixedWidth] = L'\0';
			for (size_t i = 0; i < fixedWidth; i++) {
				buff[i] = L'0';
			}
			String s (buff);
			del_a(buff);
			return s;
		}
		
		Collections::Stack<wchar_t> stack;
		
		bool isNegative = Value() < m_Zero;
		
		int_t value = Value() * ( isNegative ? -1 : 1 );
		int_t i = 30;
		
		for (; value && i; --i, value /= radix) {
			wchar_t ch = Int_Digits.WcharAt (value % radix);
			stack.Push (ch);
		}
		
		if (isNegative) {
			stack.Push (L'-');
		}
		
		size_t strLength = stack.get_Height();
		
		if (strLength < fixedWidth) {
			for (; strLength < fixedWidth ; strLength++) {
				stack.Push (L'0');
			}
		}
		/*
		else if (strLength > fixedWidth) {
			for (; strLength > fixedWidth ; strLength--) {
				stack.Pop();
			}
		}
		*/
		wchar_t* digits = new wchar_t[strLength + 1];
		digits[strLength] = L'\0';
		
		i = 0;
		while (stack.get_Height() ) {
			wchar_t ch = stack.Pop();
			digits[i] = ch;
			i++;
		}
		String s (digits);
		del_a(digits);
		
		return s;
	}
	
	/*template <typename int_t>
	IntBase<int_t>::operator unsigned long() const {
		return (unsigned long)Value();
	}*/
	
	template <typename int_t>
	IntBase<int_t> IntBase<int_t>::power (int iPower) const {
		if (iPower < 0) {
			throw EInvalidArgument (L"iPower");
		}
		
		IntBase<int_t> accum = 1;
		for (int i = 0; i < iPower; i++) {
			accum = accum * (*this);
		}
		return accum;
	}
	
	template <typename int_t>
	IntBase<int_t> IntBase<int_t>::Parse (const String& s, Radix radix = radixDec) {
		IntBase<int_t> accum = 0;
		
		size_t iLength = s.get_Length();
		size_t i = 0;
		if (!iLength) {
			String msg = String (getTypeName() ) + L": Cannot parse empty string";
			throw EInvalidFormat (msg.ToWChars() );
		}
		bool isNegative = false;
		if (s.At (0) == L'-') {
			isNegative = true;
			if (iLength == 1) {
				String msg = String (getTypeName() ) + L": Cannot parse string '" + s + L"'";
				throw EInvalidFormat (msg.ToWChars() );
			}
		}
		
		for (i = isNegative ? 1 : 0; i < iLength; i++) {
			Character ch = s.At (i);
			int iDigit = Int_Digits.IndexOf (ch);
			if (iDigit < 0 || iDigit >= radix) {
				String msg = String (getTypeName() ) + L": Cannot parse string '" + s + L"'";
				throw EInvalidFormat (msg.ToWChars() );
			}
			
			IntBase<long> iDigitsValue
			= iDigit * IntBase<long> (radix).power (iLength - i - 1);
			accum = accum + iDigitsValue;
		}
		
		return accum * (isNegative ? -1 : 1);
	}
}

#endif // _Ape__IntBase_h
